/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package TimeRecording.TimeClockServer.messages;

import TimeRecording.TimeClockServer.MessageFormatter;
import java.text.SimpleDateFormat;

/**
 * This class contains details of a clock terminal message whether incoming or
 * outgoing.  A number of static member variables are declared for common fixed
 * messages.
 *
 * @author dpatchett
 */
public class ClockMessage {

    protected final static SimpleDateFormat DATE_FORMATTER =
                new SimpleDateFormat("yyMMddHHmmss");

    public final static byte EOT = 0x04;

    public final static ClockMessage ACK =
            new ClockMessage("[HDR] ATS ACK[RS]");

    public final static ClockMessage NOISE =
            new ClockMessage("S[RS]");

    public final static ClockMessage KEEP_ALIVE =
            new ClockMessage( "[HDR] ATS");

    public final static ClockMessage REQUEST_HISTORY =
            new ClockMessage( "[HDR]R01[RS]");

    public final static ClockMessage REQUEST_TIME =
            new ClockMessage( "[HDR]TR00[RS]");

    // The raw content of the message including any header/footer
    private byte[] msgBytes;

    /**
     * Constructs a new message from it's string representation.  Control code
     * mnemonics are converted to the corresponding ASCII characters suitable
     * for transmission to the clock terminal.
     *
     * This constructor is typically used in the creation of outgoing messages.
     *
     * @param string a readable message including mnemonics where required
     */
    public ClockMessage(String string) {
        this(MessageFormatter.textToBytes(string));
    }

    /**
     * Constructs a new message from it's raw bytes.  This constructor is
     * typically used in the creation (for processing) of incoming messages.
     * If we're using the EOT marker (which we will be) then append one if there
     * isn't one on there already.
     *
     * @param msgBytes the message in its raw (byte) form
     */
    public ClockMessage(byte[] msgBytes) {
//        if (ClockServer.USING_EOT_MARKER && msgBytes[msgBytes.length - 1] != EOT) {
//            this.msgBytes = Arrays.copyOf(msgBytes, msgBytes.length + 1);
//            this.msgBytes[this.msgBytes.length-1] = EOT;
//        } else {
            this.msgBytes = msgBytes;
//        }
    }

    /**
     * Prints the message in a friendly format replacing any control codes with
     * suitable mnemonics.
     *
     * @return the message content in human reaadable form
     */
    @Override
    public String toString() {
        return MessageFormatter.bytesToText(msgBytes);
    }

    /**
     * Gets the message content in raw byte (machine friendly) form.
     *
     * @return the bytes that constitute the message
     */
    public final byte[] getBytes() {
        return msgBytes;
    }

    /**
     * This method tests this message against the supplied one to see if there
     * is a match.  Note that the match only checks as many bytes as are present
     * in the supplied message.  Therefore, this message may be significantly
     * longer than that supplied yet still return a match.
     *
     * @param match the message or partial message to check for a match
     * @return a boolean indicating there is a match (or not)
     */
    public boolean matches(ClockMessage match) {

        // Have we got at least enough bytes to match with the message supplied?
        if (msgBytes.length < match.msgBytes.length) {
            return false;
        }

        // Okay, try to match the bytes; just one difference and we return false
        for (int index = 0; index < match.msgBytes.length; index++) {
            if (msgBytes[index] != match.msgBytes[index]) {
                return false;
            }
        }

        // No mismatched bytes so the check is positive
        return true;
    }

    /**
     * This simple method check to see if this message requires an
     * acknowledgement.
     *
     * @return a boolean indicating an acknowledgement is required (or not)
     */
    public boolean ackRequired() {
        return !this.matches(ACK) && !this.matches(NOISE) && !this.matches(KEEP_ALIVE);
    }

    /**
     * This simple method tests to see whether or not the supplied message
     * requires some sort of processing or whether it is just noise (e.g. the
     * regular terminal heartbeat).
     *
     * @return a boolean indicating this message is NOT noise
     */
    public boolean notNoise() {
        return !this.matches(NOISE) && !this.matches(KEEP_ALIVE);
    }

    /**
     * A simple test to see if the message is just a plain old ACK.
     *
     * @return a boolean indicating the message is an ACK (or not)
     */
    public boolean isAck() {
        return this.matches(ACK);
    }
}
